home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Power Programmierung
/
Power-Programmierung (Tewi)(1994).iso
/
magazine
/
progjour
/
1990
/
05
/
virt.c
< prev
next >
Wrap
C/C++ Source or Header
|
1990-07-25
|
18KB
|
473 lines
#include <stdio.h>
#include <io.h>
#include <fcntl.h> /* for Microsoft-compatible O_RDWR */
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
/* VIRT.C. This file contains C workhorse functions that manipulate
* disk-based virtual memory. */
#ifdef DEBUG
#define D(x) x
#define ND(x)
#else
#define D(x)
#define ND(x) x
#endif
#define READ 1 /* first argument to block_io() */
#define WRITE 0 /* " */
#define TNAME "$$$$vmem.tmp" /* name used for virtual-memory */
/* file. TMP environment prefix */
#define BLK_SIZE 256 /* block size (bytes) */
#define SIGNATURE 0x5a5a /* Identifies vmem's as valid. */
/* Stored in 15-bit unsigned. */
static char Vname[128]; /* path name to vmem file */
static unsigned Filesize = 0; /* size of virt mem file blocks) */
static int Vfd = -1; /* File handle for Vname */
typedef struct hdr {
unsigned nblocks; /* Size of array in blocks. */
unsigned offset; /* Offset (blocks) to block start. */
union { struct hdr *h; /* Next element of freelist. */
struct vmem *v; /* Additional info if in use. */
} next;
} hdr;
typedef struct vmem {
unsigned signature:15; /* 0x5a5a if structure valid */
unsigned dirty :1; /* true if buf modified since read */
unsigned ele_size; /* size of one element */
unsigned ele_per_blk; /* number of elements in a block */
long num_ele; /* number of elements in array */
unsigned cblock; /* block currently in buffer */
char *buf; /* Swap buffer */
} vmem;
#define PUBLIC /* for documentation */
#define PRIVATE static
PRIVATE hdr *Freelist = NULL; /* Pointer to free list--circular */
/* linked list, initially empty */
extern int vopen ( void );
extern int vclose ( void );
extern hdr *vmalloc (long num_ele,unsigned int ele_size);
extern int vfree (hdr *p);
extern void *vread (hdr *p,long index);
extern void *vwrite (hdr *p,long index,char *this);
extern long vele (hdr *p);
static hdr *new_hdr (unsigned nblocks, unsigned offset);
static unsigned enlarge (unsigned blocks_reqd);
static hdr *add_vmem (long num_ele, int ele_size, hdr *header);
static int load_block (hdr *p, unsigned req_block);
static int block_io (int doread, unsigned block, char *buf);
PUBLIC int vopen() {
/* Open the virtual-memory system: Create the virtual-memory file and open
* it for read and write in unbuffered mode. Return true on success, false
* on failure (errno is set to an appropriate value in this case). The
* file is called "$$$$vmem.tmp", and it will be put into the directory
* specified in the TMP environment if this environment is present. E2BIG
* is used if the path in the TMP environment is too long. */
char *env;
int len;
if ( !(env = getenv( "TMP" )) ) /* Assemble file name */
strcpy( Vname, TNAME ); /* No TMP environment */
else {
/* If a TMP environment exists, use it as a prefix in the file name,
* adding a trailing / if necessary. */
if ((len = strlen( env )) > sizeof(Vname) - 16) {
errno = E2BIG;
return 0;
}
sprintf( Vname,
( len>=2 && env[len-2]!='/' && env[len-2]!='\\' ) ?
"%s/%s" : "%s%s", env, TNAME );
}
/* Open the file, truncating to zero length if it exists. This is done
* with creat rather than open to avoid portability problems between
* Microsoft C and Zortech. The file is opened only for writing, however,
* so the file has to be closed and reopened for reading and writing. */
if ( (Vfd = creat(Vname, S_IREAD | S_IWRITE)) == -1 )
return 0;
close( Vfd );
if ( (Vfd = open(Vname, O_RDWR | O_BINARY )) == -1 )
return 0;
return 1;
}
PUBLIC int vclose() {
/* Close the virtual-memory system and delete vmem file. Return 1 on
* success, 0 on failure (and errno is set to something reasonable) EBADF
* is used if no previous vopen() has been issued. */
hdr *p, *newp;
if ( Vfd == -1 ) { /* the file isn't open */
errno = EBADF;
return 0;
}
else {
if ( close( Vfd ) == -1 ) /* close & delete file */
return 0;
Vfd = -1;
ND( if ( unlink( Vname ) == -1 ) )
ND( return 0; )
if ( Freelist ) { /* Empty the free list */
p = Freelist;
do {
newp = p->next.h;
free( p );
} while ( (p = newp) != Freelist );
Freelist = NULL;
}
}
return 1;
}
PUBLIC hdr *vmalloc( long num_ele, unsigned ele_size ) {
/* Allocate a virtual array, having num_ele, each of ele_size bytes.
* Return a pointer to use in subsequent operations. The size of the
* internal buffer used to access elements is controlled by vopen().
* Return NULL if no virtual memory is available or there is a seek error.
* Errno will have an appropriate value in it in this case. */
hdr *prev, *cur, *new, *eof_block ;
unsigned ele_per_block; /* elements per block */
unsigned blocks_reqd; /* number of blocks required */
D( printf("Entering vmalloc(%ld,%u). ", num_ele, ele_size); )
D( pfreelist(); )
ele_per_block = BLK_SIZE / ele_size ;
blocks_reqd = num_ele/ele_per_block + (num_ele % ele_per_block != 0);
if ( Freelist ) { /* If there is a freelist */
prev = Freelist;
cur = Freelist->next.h;
eof_block = NULL;
while ( 1 ) {
do {
if ( cur->offset + cur->nblocks == Filesize )
eof_block = cur; /* last block in file */
if ( cur->nblocks >= blocks_reqd ) {
Freelist = prev; /* it's big enough */
if ( cur->nblocks > blocks_reqd ) {
cur->nblocks -= blocks_reqd;
if ( new = new_hdr( blocks_reqd,
cur->offset + cur->nblocks) )
new = add_vmem( num_ele, ele_size, new );
goto end;
}
else { /* block is exact size */
if ( cur->next.h == cur ) /* no successor */
Freelist = NULL;
else
prev->next.h = cur->next.h;
new = add_vmem( num_ele, ele_size, cur );
goto end;
}
}
prev = cur; /* go to next freelist element */
cur = cur->next.h;
} while ( prev != Freelist );
/* If we get here, no block in the freelist was large enough. If a block in
* the free list was at the end of the file, expand it. */
if ( !eof_block )
break;
else if ( !enlarge(blocks_reqd - eof_block->nblocks) ) {
new = NULL;
goto end;
}
else
eof_block->nblocks = blocks_reqd;
/* Loop back up. The do/while will catch the newly-expanded object. */
} /* end while ( 1 ) */
} /* end if ( Freelist ) */
/* If we get here, either there was no freelist, or no block big enough in
* the freelist and we couldn't expand the last block. Make a new block. */
if ( !(new = new_hdr(blocks_reqd, Filesize)) )
goto end;
if ( !enlarge( blocks_reqd ) ) { /* changes Filesize */
free( new );
new = NULL;
}
new = add_vmem( num_ele, ele_size, new );
end:
D( printf("Leaving vmalloc(%ld,%u). Return:\n", num_ele, ele_size); )
D( pheader(new); )
D( pfreelist(); )
D( printf("-----------------------------------------\n"); )
return new;
}
PRIVATE hdr *new_hdr( unsigned nblocks, unsigned offset ) {
hdr *new;
if ( !(new = (hdr *) malloc(sizeof(hdr))) ) {
errno = ENOMEM;
return NULL;
}
new->nblocks = nblocks;
new->offset = offset ;
return new;
}
PRIVATE unsigned enlarge( unsigned blocks_reqd ) {
/* Make the file blocks_reqd bytes larger by seeking that many bytes past
* end of file. Adjust the global Filesize to indicate the real file size.
* Return new filesize (in blocks) or 0 if the file couldn't be expanded.
* Note that the second argument to lseek is an offset. Consequently, it's
* one less than the count, thus the -1 in the assignment to position. The
* 1 must be added back in the return statement, however, to make the count
* work. The write() call both physically expands the file and advances the
* file pointer one notch. This isn't necessary under UNIX, but MS-DOS
* needs it. */
unsigned size_in_blocks;
long real_size;
long position ;
position = (long)blocks_reqd * BLK_SIZE - 1;
if ( (real_size = lseek(Vfd, position, SEEK_END)) == -1) {
errno = ENOMEM;
return 0L;
}
if ( write( Vfd, "", 1 ) == -1 )
return 0L;
++real_size;
return( (real_size/BLK_SIZE > (unsigned)~0) ? 0 :
(Filesize = (unsigned)(real_size / BLK_SIZE)) );
}
PRIVATE hdr *add_vmem( long num_ele, int ele_size, hdr *header ) {
/* Create a new vmem structure and attach it to the header. Return header
* argument or NULL if there was a problem. */
vmem *p;
if ( !(p = (vmem *)malloc( sizeof(vmem))) ) { /* get the vmem */
errno = ENOMEM;
return NULL;
}
else if ( !(p->buf = (char *)malloc(BLK_SIZE)) ) { /* & swap buf. */
errno = ENOMEM;
return NULL;
}
else {
header->next.v = p;
p->signature = SIGNATURE;
p->dirty = 0;
p->ele_size = ele_size;
p->ele_per_blk = BLK_SIZE / ele_size;
p->num_ele = num_ele;
p->cblock = 0 ;
return header;
}
}
PUBLIC int vfree( hdr *p ) {
/* Free virtual array pointed to by p. Return true on success, false if the
* input pointer was invalid (if the first element of the structure wasn't
* the SIGNATURE). Errno is set to EBADF in this case. */
int rval = 1;
hdr *cur, *h;
vmem *v = p->next.v;
D( printf("Entering vfree(0x%x). ", p ); )
D( pfreelist(); )
if ( v->signature == SIGNATURE )
v->signature = 0;
else {
errno = EBADF;
rval = 0;
goto end;
}
free( v->buf ); /* discard the swap buffer */
free( v ); /* discard the vmem component */
if ( !Freelist ) { /* Free list is empty. Create a new one. */
Freelist = p->next.h = p;
goto end; /* return default value */
}
/* Find out where the newly freed node goes: The list is a circular linked
* list arranged in ascending order of offset. Since it's circular, there
* will be one place where cur->offset > cur->next.h->offset (see 1,
* below). We have to test for the normal situation where the new node
* comes between the current node and its successor. The rest of the
* following if statement tests for the corner situations when the new
* node's offset is either larger (at 2) or smaller (at 3) than anything
* currently in the list. */
for ( cur = Freelist ; 1 ; cur = cur->next.h ) {
if ( ( cur->offset < p->offset &&
p->offset < cur->next.h->offset
) ||
( cur->offset >= cur->next.h->offset && /* 1 */
( cur->offset < p->offset || /* 2 */
p->offset < cur->next.h->offset /* 3 */
)
)
)
break;
}
/* Now link the node into the list, coalescing if necessary. First
* handle degraded case of a free list with only one element. */
if ( cur == cur->next.h ) { /* only block in chain */
if ( p->offset + p->nblocks == cur->offset ) {
cur->offset = p->offset;
cur->nblocks += p->nblocks;
free( p );
goto end;
}
else if ( cur->offset + cur->nblocks == p->offset ) {
cur->nblocks += p->nblocks;
free( p );
goto end;
}
}
/* Now handle normal case. Link or coalesce to the right first,
* then to the left. */
if ( p->offset + p->nblocks == cur->next.h->offset ) {
/* The blocks are adjacent. Coalesce and free the extra header. */
p->nblocks += cur->next.h->nblocks;
p->next.h = cur->next.h->next.h;
free( cur->next.h );
}
else
p->next.h = cur->next.h;
if ( cur->offset + cur->nblocks == p->offset ) {
cur->nblocks += p->nblocks; /* Adjacent */
cur->next.h = p->next.h;
free( p );
}
else
cur->next.h = p;
end:
D( printf("Leaving vfree(0x%x). Returning %d. ", p, rval ); )
D( pfreelist(); )
D( printf("-------------------------------------------\n"); )
return rval;
}
PUBLIC void *vread ( hdr *p, long index ) {
/* Returns pointer to array element at indicated offset from the virtual
* array pointed to by p. Returns NULL if offset is out of bounds or the
* block couldn't be loaded (errno is set too). */
unsigned block_num; /* block in which index is found */
vmem *v = p->next.v; /* vmem component of *p */
if ( index < 0 || index > v->num_ele ) {
errno = ERANGE; /* index is out of range */
return 0;
}
if ( v->signature != SIGNATURE || Vfd == -1 ) {
errno = EBADF ; /* signature doesn't match */
return 0;
}
block_num = index / v->ele_per_blk ;
if ( !load_block( p, block_num ) )
return NULL;
/* get the pointer:
* (1) adjust index so it's relative to the start of the current block
* (2) Multiply by the element size to get a byte offset.
* (3) Add to base address of buffer to get pointer. */
index -= block_num * v->ele_per_blk; /* (1) */
index *= v->ele_size; /* (2) */
return( v->buf + index ); /* (3) */
}
PUBLIC void *vwrite( hdr *p, long index, char *this ) {
/* Transfer *this to the virtual array at the indicated index. Return a
* pointer to the actual memory location for *this or NULL on an i/o
* failure or a bad "p" argument. */
int i;
char *bp;
void *startp;
vmem *v = p->next.v ;
if ( startp = bp = vread( p, index ) ) {
for ( i = v->ele_size; --i >= 0 ;)
*bp++ = *this++;
v->dirty = 1;
}
return startp;
}
PRIVATE int load_block( hdr *p, unsigned req_block ) {
/* Load a block. The base offset is in p->offset and req_block is
* the additional offset needed to get to the correct block. */
vmem *v = p->next.v;
if ( req_block != v->cblock ) {
if ( v->dirty && ! block_io ( WRITE,
p->offset + v->cblock, v->buf) )
return 0;
if ( !block_io(READ, p->offset + req_block, v->buf))
return 0;
v->dirty = 0;
v->cblock = req_block;
}
return 1;
}
PRIVATE int block_io( int doread, unsigned block_num, char *buf ) {
/* Read or write a block, depending on the value of doread, into or from
* buf. block_num is the absolute block number. */
size_t got; /* wrote or read this many bytes */
long here; /* for debugging, make sure we got there */
here = lseek( Vfd, (long)block_num * BLK_SIZE, SEEK_SET);
if ( here== -1 )
return 0;
got = doread ? read (Vfd, buf, (size_t) BLK_SIZE)
: write(Vfd, buf, (size_t) BLK_SIZE) ;
if ( got == -1 )
return 0;
/* it's an error if we didn't write the block, it's not an error if we
* didn't read it, however. We may be trying to read a block at the end
* of file that hasn't been written to yet-not a problem under UNIX but
* it is under DOS. */
return( doread ? 1 : (got == BLK_SIZE) );
}
PUBLIC long vele ( hdr *p ) { return p->next.v->num_ele; }
PUBLIC void vdirty( hdr *p ) { p->next.v->dirty = 1; }
#ifdef DEBUG
pfreelist() {
hdr *p;
if ( !(p = Freelist) )
printf("Freelist is empty.\n");
else {
printf("Freelist is:\n");
do { pheader(p);
} while ( (p = p->next.h) != Freelist );
}
}
pheader( hdr *p ) {
if ( !p )
printf("NULL");
else
printf("hdr x%x: offset=%u nblocks=%u next=0x%x%s\n",
p, p->offset, p->nblocks, p->next.h,
p == Freelist ? " <-Freelist" : "" );
}
#endif /* ifdef DEBUG */